use crate::entities::{resource, article_resource, product_resource};
use sea_orm::entity::prelude::*;
use crate::errors::{AppError, Result};
use crate::models::{Resource, UploadResponse, ResourceAssignmentRequest};
use actix_multipart::Multipart;

use futures_util::TryStreamExt;
use sea_orm::*;

use uuid::Uuid;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use std::path::Path;

pub struct ResourceService;

impl ResourceService {
    pub async fn upload_files(
        db: &DatabaseConnection,
        mut payload: Multipart,
        upload_dir: &str,
    ) -> Result<UploadResponse> {
        let mut uploaded_resources = Vec::new();

        // Ensure upload directory exists
        fs::create_dir_all(upload_dir).await?;

        while let Some(mut field) = payload.try_next().await? {
            let content_disposition = field.content_disposition();
            let file_name = content_disposition
                .get_filename()
                .ok_or_else(|| AppError::Validation("No filename provided".to_string()))?
                .to_string();

            let file_extension = Path::new(&file_name)
                .extension()
                .and_then(|ext| ext.to_str())
                .unwrap_or("")
                .to_string();

            let mime_type = field.content_type().map(|ct| ct.to_string()).unwrap_or_else(|| "application/octet-stream".to_string());

            // Generate unique filename
            let unique_filename = format!("{}-{}", Uuid::new_v4(), file_name);
            let file_path = format!("{}/{}", upload_dir, unique_filename);
            let public_url = format!("/uploads/{}", unique_filename);

            // Save file
            let mut file = fs::File::create(&file_path).await?;
            let mut file_size = 0i64;

            while let Some(chunk) = field.try_next().await? {
                file_size += chunk.len() as i64;
                file.write_all(&chunk).await?;
            }
            file.flush().await?;

            // Save to database
            let new_resource = resource::ActiveModel {
                id: Set(Uuid::new_v4()),
                file_name: Set(file_name),
                extension: Set(file_extension),
                url: Set(public_url),
                file_size: Set(file_size),
                mime_type: Set(mime_type),
                created_at: Set(chrono::Utc::now()),
                updated_at: Set(chrono::Utc::now()),
            };

            let saved_resource = new_resource.insert(db).await?;

            uploaded_resources.push(Resource {
                id: saved_resource.id,
                file_name: saved_resource.file_name,
                extension: saved_resource.extension,
                url: saved_resource.url,
                file_size: saved_resource.file_size,
                mime_type: saved_resource.mime_type,
                created_at: saved_resource.created_at,
                updated_at: saved_resource.updated_at,
            });
        }

        Ok(UploadResponse {
            resources: uploaded_resources.clone(),
            total_count: uploaded_resources.len(),
        })
    }

    pub async fn get_by_id(db: &DatabaseConnection, id: Uuid) -> Result<Resource> {
        let resource = resource::Entity::find_by_id(id)
            .one(db)
            .await?
            .ok_or_else(|| AppError::NotFound {
                entity: "Resource".to_string(),
                id,
            })?;

        Ok(Resource {
            id: resource.id,
            file_name: resource.file_name,
            extension: resource.extension,
            url: resource.url,
            file_size: resource.file_size,
            mime_type: resource.mime_type,
            created_at: resource.created_at,
            updated_at: resource.updated_at,
        })
    }

    pub async fn delete_resource(db: &DatabaseConnection, id: Uuid) -> Result<()> {
        // Check if resource exists
        let resource = resource::Entity::find_by_id(id)
            .one(db)
            .await?
            .ok_or_else(|| AppError::NotFound {
                entity: "Resource".to_string(),
                id,
            })?;

        // Delete from database
        resource::Entity::delete_by_id(id).exec(db).await?;

        // Delete file from filesystem
        let file_path = resource.url.trim_start_matches("/uploads/");
        let full_path = format!("uploads/{}", file_path);
        
        if Path::new(&full_path).exists() {
            fs::remove_file(&full_path).await?;
        }

        Ok(())
    }

    pub async fn delete_resource_cascade(db: &DatabaseConnection, id: Uuid) -> Result<()> {
        // Delete article resource associations
        article_resource::Entity::delete_many()
            .filter(article_resource::Column::ResourceId.eq(id))
            .exec(db)
            .await?;

        // Delete product resource associations
        product_resource::Entity::delete_many()
            .filter(product_resource::Column::ResourceId.eq(id))
            .exec(db)
            .await?;

        // Delete the resource itself
        Self::delete_resource(db, id).await?;

        Ok(())
    }

    pub async fn assign_to_article(
        db: &DatabaseConnection,
        article_id: Uuid,
        language_code: &str,
        request: ResourceAssignmentRequest,
    ) -> Result<()> {
        // Check if resource exists
        let resource_exists = resource::Entity::find_by_id(request.resource_id)
            .one(db)
            .await?
            .is_some();

        if !resource_exists {
            return Err(AppError::NotFound {
                entity: "Resource".to_string(),
                id: request.resource_id,
            });
        }

        // For articles, only one resource per language is allowed (featured image)
        // So we first delete any existing assignment for this language
        article_resource::Entity::delete_many()
            .filter(article_resource::Column::ArticleId.eq(article_id))
            .filter(article_resource::Column::LanguageCode.eq(language_code))
            .exec(db)
            .await?;

        // Create new assignment
        let new_assignment = article_resource::ActiveModel {
            id: Set(Uuid::new_v4()),
            article_id: Set(article_id),
            language_code: Set(language_code.to_string()),
            resource_id: Set(request.resource_id),
            resource_type: Set(request.resource_type),
            sort_order: Set(request.sort_order.unwrap_or(0)),
            created_at: Set(chrono::Utc::now()),
            updated_at: Set(chrono::Utc::now()),
        };

        new_assignment.insert(db).await?;

        Ok(())
    }

    pub async fn assign_to_product(
        db: &DatabaseConnection,
        product_id: Uuid,
        language_code: &str,
        request: ResourceAssignmentRequest,
    ) -> Result<()> {
        // Check if resource exists
        let resource_exists = resource::Entity::find_by_id(request.resource_id)
            .one(db)
            .await?
            .is_some();

        if !resource_exists {
            return Err(AppError::NotFound {
                entity: "Resource".to_string(),
                id: request.resource_id,
            });
        }

        // Create new assignment (products can have multiple resources)
        let new_assignment = product_resource::ActiveModel {
            id: Set(Uuid::new_v4()),
            product_id: Set(product_id),
            language_code: Set(language_code.to_string()),
            resource_id: Set(request.resource_id),
            resource_type: Set(request.resource_type),
            sort_order: Set(request.sort_order.unwrap_or(0)),
            created_at: Set(chrono::Utc::now()),
            updated_at: Set(chrono::Utc::now()),
        };

        new_assignment.insert(db).await?;

        Ok(())
    }

    pub async fn remove_from_article(
        db: &DatabaseConnection,
        article_id: Uuid,
        language_code: &str,
        resource_id: Uuid,
    ) -> Result<()> {
        let result = article_resource::Entity::delete_many()
            .filter(article_resource::Column::ArticleId.eq(article_id))
            .filter(article_resource::Column::LanguageCode.eq(language_code))
            .filter(article_resource::Column::ResourceId.eq(resource_id))
            .exec(db)
            .await?;

        if result.rows_affected == 0 {
            return Err(AppError::NotFound {
                entity: "ArticleResource".to_string(),
                id: Uuid::new_v4(),
            });
        }

        Ok(())
    }

    pub async fn remove_from_product(
        db: &DatabaseConnection,
        product_id: Uuid,
        language_code: &str,
        resource_id: Uuid,
    ) -> Result<()> {
        let result = product_resource::Entity::delete_many()
            .filter(product_resource::Column::ProductId.eq(product_id))
            .filter(product_resource::Column::LanguageCode.eq(language_code))
            .filter(product_resource::Column::ResourceId.eq(resource_id))
            .exec(db)
            .await?;

        if result.rows_affected == 0 {
            return Err(AppError::NotFound {
                entity: "ProductResource".to_string(),
                id: Uuid::new_v4(),
            });
        }

        Ok(())
    }

    pub async fn get_article_resources(
        db: &DatabaseConnection,
        article_id: Uuid,
        language_code: Option<&str>,
    ) -> Result<Vec<crate::models::ArticleResource>> {
        let mut query = article_resource::Entity::find()
            .filter(article_resource::Column::ArticleId.eq(article_id));

        if let Some(lang) = language_code {
            query = query.filter(article_resource::Column::LanguageCode.eq(lang));
        }

        let article_resources = query.all(db).await?;

        let mut result = Vec::new();
        for article_res in article_resources {
            // Get the related resource
            let resource = resource::Entity::find_by_id(article_res.resource_id)
                .one(db)
                .await?
                .ok_or_else(|| AppError::NotFound {
                    entity: "Resource".to_string(),
                    id: article_res.resource_id,
                })?;

            result.push(crate::models::ArticleResource {
                id: article_res.id,
                article_id: article_res.article_id,
                language_code: article_res.language_code,
                resource_id: article_res.resource_id,
                resource_type: article_res.resource_type,
                sort_order: article_res.sort_order,
                resource: crate::models::Resource {
                    id: resource.id,
                    file_name: resource.file_name,
                    extension: resource.extension,
                    url: resource.url,
                    file_size: resource.file_size,
                    mime_type: resource.mime_type,
                    created_at: resource.created_at,
                    updated_at: resource.updated_at,
                },
            });
        }

        Ok(result)
    }

    pub async fn get_product_resources(
        db: &DatabaseConnection,
        product_id: Uuid,
        language_code: Option<&str>,
    ) -> Result<Vec<crate::models::ProductResource>> {
        let mut query = product_resource::Entity::find()
            .filter(product_resource::Column::ProductId.eq(product_id));

        if let Some(lang) = language_code {
            query = query.filter(product_resource::Column::LanguageCode.eq(lang));
        }

        let product_resources = query.all(db).await?;

        let mut result = Vec::new();
        for product_res in product_resources {
            // Get the related resource
            let resource = resource::Entity::find_by_id(product_res.resource_id)
                .one(db)
                .await?
                .ok_or_else(|| AppError::NotFound {
                    entity: "Resource".to_string(),
                    id: product_res.resource_id,
                })?;

            result.push(crate::models::ProductResource {
                id: product_res.id,
                product_id: product_res.product_id,
                language_code: product_res.language_code,
                resource_id: product_res.resource_id,
                resource_type: product_res.resource_type,
                sort_order: product_res.sort_order,
                resource: crate::models::Resource {
                    id: resource.id,
                    file_name: resource.file_name,
                    extension: resource.extension,
                    url: resource.url,
                    file_size: resource.file_size,
                    mime_type: resource.mime_type,
                    created_at: resource.created_at,
                    updated_at: resource.updated_at,
                },
            });
        }

        Ok(result)
    }
}